#include <bits/stdc++.h>

using namespace std;

// 风格：使用常量定义数组大小，略大于N的最大值
const int N = 2e5 + 5;

// 风格：定义点的结构体，类似于你的 'node' 结构体
struct point {
    int x, y;
} p[N];

// 风格：使用全局变量存储问题状态
int n;
int tmp[N];       // 临时数组，用于存储“候选带”中点的索引
double res = 1e18; // 用一个很大的浮点数作为答案的初始值，1e18足够安全

// 辅助函数，根据点的索引计算两点之间的距离
double dist(int i, int j) {
    // 强制类型转换为double进行计算，以避免溢出并保持精度
    double dx = (double)p[i].x - p[j].x;
    double dy = (double)p[i].y - p[j].y;
    return sqrt(dx * dx + dy * dy);
}

// 比较函数，用于按x坐标对点进行排序。
// 这是分治算法的初始步骤所必需的。
bool cmpx(const point &a, const point &b) {
    if (a.x != b.x) return a.x < b.x;
    return a.y < b.y; // 当x坐标相同时，按y坐标排序，以处理垂直线上的点
}

// 比较函数，用于根据点的y坐标对索引进行排序。
// 这在“合并”步骤中使用。
bool cmpy(int a, int b) {
    return p[a].y < p[b].y;
}

// 核心的分治函数
void solve(int l, int r) {
    // 递归边界：如果区间内的点数太少，直接返回。
    // 跨区域的三角形将在合并步骤中处理。
    if (l >= r - 1) return;

    // 分割(Divide)：找到中点以分割点集
    int mid = (l + r) >> 1;
    double mid_x = p[mid].x; // 分割线的x坐标

    // 解决(Conquer)：递归地解决左半部分和右半部分
    solve(l, mid);
    solve(mid + 1, r);

    // 合并(Combine)：寻找跨越分割线的最小周长三角形。
    // 我们只需要考虑分割线周围宽度为 'res' 的“候选带”内的点。
    int m = 0;
    double lim = res / 2.0;

    // 1. 从当前区间 [l, r] 中筛选出所有在候选带内的点。
    for (int i = l; i <= r; ++i) {
        if (abs((double)p[i].x - mid_x) <= lim) {
            tmp[++m] = i; // 存储点的原始索引
        }
    }

    // 2. 将候选带中的点按y坐标排序
    sort(tmp + 1, tmp + m + 1, cmpy);

    // 3. 遍历候选带中的点，检查是否存在更小的三角形。
    // 这里使用了一个关键的优化（剪枝）。
    for (int i = 1; i <= m; ++i) {
        for (int j = i + 1; j <= m; ++j) {
            // 剪枝：如果点i和点j的y坐标差已经大于等于 lim (即 res/2)，
            // 那么任何包含它们的三角形都不太可能更优。
            // 这是因为两点距离必然大于等于它们的y坐标差，而任何更优三角形的边长都必须小于 lim。
            if ((double)p[tmp[j]].y - p[tmp[i]].y >= lim) {
                break;
            }
            // 经证明，在j之后需要检查的点的数量是一个很小的常数。
            for (int k = j + 1; k <= m; ++k) {
                // 同样的剪枝策略
                if ((double)p[tmp[k]].y - p[tmp[i]].y >= lim) {
                    break;
                }
                double current_perimeter = dist(tmp[i], tmp[j]) + dist(tmp[j], tmp[k]) + dist(tmp[k], tmp[i]);
                res = min(res, current_perimeter);
            }
        }
    }
}

int main() {
    #ifdef LOCAL
        freopen("data.in", "r", stdin);
        freopen("data.out", "w", stdout);
        auto start_time = chrono::high_resolution_clock::now();
    #endif

    // 本题使用 scanf/printf，所以关闭同步流不是必须的。
    // ios::sync_with_stdio(false);
    // cin.tie(0); cout.tie(0);
    
    // ================ 你的代码从这里开始 ================
    
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d %d", &p[i].x, &p[i].y);
    }

    // 算法的第一步：将所有点按x坐标排序
    sort(p + 1, p + n + 1, cmpx);

    // 对整个点集启动分治过程
    solve(1, n);

    // 按6位小数格式化输出最终答案
    printf("%.6f\n", res);

    // ================ 你的代码到这里结束 ================
    
    #ifdef LOCAL
        auto end_time = chrono::high_resolution_clock::now();
        auto duration = chrono::duration_cast<chrono::milliseconds>(end_time - start_time);
        cerr << "Time cost: " << duration.count() << " ms" << endl;
    #endif
    
    return 0;
}